import { Commit, LineStats } from './gitService';

export interface FeaturePoint {
    title: string;
    commits: Commit[];
    category: string;
}

export interface CommitSummary {
    features: FeaturePoint[];
    period: {
        from: Date;
        to: Date;
    };
    totalCommits: number;
    categories: { [key: string]: number };
    lineStats: LineStats;
    contributorCount: number;
    contributors: string[];
}

export class SummaryGenerator {
    // Categories for classifying commits
    private readonly CATEGORIES = {
        FEATURE: ['feat', 'feature', 'add', 'implement'],
        FIX: ['fix', 'bug', 'resolve', 'solved'],
        REFACTOR: ['refactor', 'improve', 'perf', 'performance'],
        DOC: ['doc', 'docs', 'document'],
        TEST: ['test', 'spec'],
        STYLE: ['style', 'format', 'ui', 'ux'],
        OTHER: []
    };

    async generateSummary(commits: Commit[], lineStats?: LineStats, contributors?: Set<string>): Promise<CommitSummary> {
        if (commits.length === 0) {
            return {
                features: [],
                period: { from: new Date(), to: new Date() },
                totalCommits: 0,
                categories: {},
                lineStats: lineStats || { added: 0, deleted: 0, modified: 0, total: 0 },
                contributorCount: contributors?.size || 0,
                contributors: contributors ? Array.from(contributors) : []
            };
        }

        // Sort commits by date (newest first)
        const sortedCommits = [...commits].sort((a, b) => b.date.getTime() - a.date.getTime());
        
        const features: FeaturePoint[] = [];
        const processedCommits = new Set<string>();
        const categoryCounts: { [key: string]: number } = {};
        
        // First pass: identify feature points by grouping related commits
        for (const commit of sortedCommits) {
            if (processedCommits.has(commit.hash)) {
                continue;
            }
            
            const category = this.classifyCommit(commit.message);
            categoryCounts[category] = (categoryCounts[category] || 0) + 1;
            
            // Skip some categories from being considered as feature points
            if (category === 'TEST' || category === 'STYLE') {
                processedCommits.add(commit.hash);
                continue;
            }
            
            // Create a feature point from this commit
            const relatedCommits = this.findRelatedCommits(sortedCommits, commit, processedCommits);
            relatedCommits.forEach(c => processedCommits.add(c.hash));
            
            const title = this.generateFeatureTitle(commit, relatedCommits);
            features.push({
                title,
                commits: [commit, ...relatedCommits],
                category
            });
        }
        
        return {
            features: this.mergeSimilarFeatures(features),
            period: {
                from: sortedCommits[sortedCommits.length - 1].date,
                to: sortedCommits[0].date
            },
            totalCommits: commits.length,
            categories: categoryCounts,
            lineStats: lineStats || { added: 0, deleted: 0, modified: 0, total: 0 },
            contributorCount: contributors?.size || 0,
            contributors: contributors ? Array.from(contributors) : []
        };
    }
    
    private classifyCommit(message: string): string {
        const lowerMessage = message.toLowerCase();
        
        for (const [category, keywords] of Object.entries(this.CATEGORIES)) {
            if (keywords.some(keyword => lowerMessage.includes(keyword))) {
                return category;
            }
        }
        
        return 'OTHER';
    }
    
    private findRelatedCommits(commits: Commit[], mainCommit: Commit, processedCommits: Set<string>): Commit[] {
        const related: Commit[] = [];
        const mainKeywords = this.extractKeywords(mainCommit.message);
        
        for (const commit of commits) {
            if (commit.hash === mainCommit.hash || processedCommits.has(commit.hash)) {
                continue;
            }
            
            const commitKeywords = this.extractKeywords(commit.message);
            const commonKeywords = mainKeywords.filter(k => commitKeywords.includes(k));
            
            // If there are enough common keywords, consider this commit related
            if (commonKeywords.length >= 2 || 
                (commonKeywords.length === 1 && commonKeywords[0].length > 5)) {
                related.push(commit);
            }
        }
        
        return related;
    }
    
    private extractKeywords(message: string): string[] {
        // Simple keyword extraction by removing common words
        const stopWords = ['and', 'the', 'to', 'a', 'an', 'in', 'on', 'for', 'of', 'with'];
        return message
            .toLowerCase()
            .replace(/[^\w\s]/g, '')
            .split(/\s+/)
            .filter(word => word.length > 2 && !stopWords.includes(word));
    }
    
    private generateFeatureTitle(mainCommit: Commit, relatedCommits: Commit[]): string {
        // Use the main commit message as a base
        let title = mainCommit.message;
        
        // Clean up common prefixes in commit messages
        title = title.replace(/^(feat|fix|docs|style|refactor|test|chore)(\([^)]+\))?:\s*/i, '');
        
        // Capitalize first letter
        title = title.charAt(0).toUpperCase() + title.slice(1);
        
        // If the title ends with a period, remove it
        if (title.endsWith('.')) {
            title = title.slice(0, -1);
        }
        
        return title;
    }
    
    private mergeSimilarFeatures(features: FeaturePoint[]): FeaturePoint[] {
        const mergedFeatures: FeaturePoint[] = [];
        const processedIndices = new Set<number>();
        
        for (let i = 0; i < features.length; i++) {
            if (processedIndices.has(i)) continue;
            
            const currentFeature = features[i];
            const similarFeatures: FeaturePoint[] = [];
            
            for (let j = i + 1; j < features.length; j++) {
                if (processedIndices.has(j)) continue;
                
                const otherFeature = features[j];
                if (this.areSimilarFeatures(currentFeature, otherFeature)) {
                    similarFeatures.push(otherFeature);
                    processedIndices.add(j);
                }
            }
            
            if (similarFeatures.length > 0) {
                // Merge all similar features
                const allCommits = [
                    ...currentFeature.commits,
                    ...similarFeatures.flatMap(f => f.commits)
                ];
                
                mergedFeatures.push({
                    title: currentFeature.title,
                    commits: allCommits,
                    category: currentFeature.category
                });
            } else {
                mergedFeatures.push(currentFeature);
            }
            
            processedIndices.add(i);
        }
        
        return mergedFeatures;
    }
    
    private areSimilarFeatures(feature1: FeaturePoint, feature2: FeaturePoint): boolean {
        const title1Words = this.extractKeywords(feature1.title);
        const title2Words = this.extractKeywords(feature2.title);
        
        // Count common words
        const commonWords = title1Words.filter(word => title2Words.includes(word));
        
        // Consider similar if they share enough words or are in the same category
        return (commonWords.length >= 2 && feature1.category === feature2.category) || 
               (commonWords.length >= 3);
    }
}
